package org.app.appjson.reader.charreader;

import org.app.appjson.exception.AppJsonException;
import org.app.appjson.reader.interfaces.CharReader;

import java.io.IOException;
import java.io.Reader;
import java.util.Objects;

/**
 * 字符读取器
 * @author lizhongsi
 * @version 1.0
 * @since 1.0
 */
public final class BufferedCharReader implements CharReader {

    private Reader reader;
    private final Object lock;
    /**
     *  待读取字符的数组位置
     *  nextChar初始化为0，指向第一个字符
     *  peek()会读取buffer[nextChar]的值，nextChar不变
     *  next()会读取buffer[nextChar]的值，nextChar++
     */
    private int nextChar;
    /* buffer数组中有效字符的个数 */
    private int nChars;
    /* 已读取的字符在全部字符串中的位置 */
    private int index;
    /** 缓冲数组 */
    private char[] buffer;

    public BufferedCharReader(Reader in) {
        this(in, 1024);
    }

    public BufferedCharReader(Reader in, int size) {
        Objects.requireNonNull(in, "Reader is null");
        if (size <= 0) {
            throw new IllegalArgumentException("Buffer size <= 0");
        }
        reader = in;
        index = -1;
        buffer = new char[size];
        lock = this;
    }

    /* 是否存在下一个字符 */
    public boolean hasNext() throws IOException {
        // buffer为空或读到buffer结尾，填充buffer
        if (nChars == 0 || nextChar > buffer.length - 1) {
            fillBuffer();
        }
        if (nChars > 0 && nextChar < nChars) {
            return true;
        }
        close();
        return false;
    }

    /* 读取当前指针位置的字符，并将nextChar指针+1 */
    public char next() throws IOException {
        char c = peek();
        nextChar++;
        index++;
        return c;
    }

    /* 预览当前指针位置的字符 */
    public char peek() throws IOException {
        if (!hasNext()) {
            throw new IllegalStateException("Document finished.");
        }
        return buffer[nextChar];
    }

    /* 填充buffer数组 */
    private void fillBuffer() throws IOException {
        nextChar = 0;//令nextChar=0，从头读取
        // 从外部的reader读取字符到buffer数组中
        nChars = reader.read(buffer, 0, buffer.length);
    }

    // 已读取的字符在全部字符串中的位置
    public int index() {
        return index;
    }

    @Override
    public void close() throws IOException {
        synchronized (lock) {
            if (reader == null)
                return;
            try {
                reader.close();
            } finally {
                reader = null;
                buffer = null;
            }
        }
    }

    @Override
    public String readString() throws AppJsonException {
        try {
            this.next();// 跳过引号
            StringBuilder sb = new StringBuilder();
            char ch;
            for (;;) {
                ch = this.peek();
                if (ch == '\\') {
                    this.next();
                    // 转义字符 escape: \" \\ \/ \b \f \n \r \t
                    char ech = this.next();
                    switch (ech) {
                        case '\"':
                            sb.append('\"');
                            break;
                        case '\\':
                            sb.append('\\');
                            break;
                        case '/':
                            sb.append('/');
                            break;
                        case 'b':
                            sb.append('\b');
                            break;
                        case 'f':
                            sb.append('\f');
                            break;
                        case 'n':
                            sb.append('\n');
                            break;
                        case 'r':
                            sb.append('\r');
                            break;
                        case 't':
                            sb.append('\t');
                            break;
                        case 'u':
                            // read an unicode uXXXX:
                            int u = 0;
                            for (int i = 0; i < 4; i++) {
                                char uch = this.next();
                                if (uch >= '0' && uch <= '9') {
                                    u = (u << 4) + (uch - '0');
                                } else if (uch >= 'a' && uch <= 'f') {
                                    u = (u << 4) + (uch - 'a') + 10;
                                } else if (uch >= 'A' && uch <= 'F') {
                                    u = (u << 4) + (uch - 'A') + 10;
                                } else {
                                    throw new AppJsonException("JsonLexer error! Unexpected char:" + uch + ",index:" + index());
                                }
                            }
                            sb.append((char) u);
                            break;
                        default:
                            throw new AppJsonException("JsonLexer error! Unexpected char:" + ch + ",index:" + index());
                    }
                } else if (ch == '\"') {
                    // end of string.
                    this.next();
                    break;
                } else if (ch == '\r' || ch == '\n') {
                    throw new AppJsonException("JsonLexer error! Unexpected char:" + ch + ",index:" + index());
                } else {
                    this.next();
                    sb.append(ch);
                }
            }
            return sb.toString();
        } catch (IOException e) {
            try {
                close();
            } catch (IOException ignore) {
            }
        }
        return null;
    }

    // 读取一个数字
    // 使用状态机实现，状态变换参考"json词法number解析状态图.jpg"
    @Override
    public String readNumber() throws AppJsonException {
        try {
            StringBuilder sb = new StringBuilder();
            byte state = 1;
            char ch;
            for (;;) {
                ch = this.peek();
                switch (state) {
                    case 1:
                        if (ch == '-') {
                            sb.append(ch);
                            this.next();
                        }
                        state = 2;
                        break;
                    case 2:
                        if (ch == '0') {
                            sb.append(ch);
                            this.next();
                            state = 3;
                        } else {
                            if (ch >= '1' && ch <= '9') {
                                sb.append(ch);
                                this.next();
                                state = 4;
                            } else {
                                throw new AppJsonException("JsonLexer error! Expect a number but read char:" + ch + ",index:" + this.index());
                            }
                        }
                        break;
                    case 3:
                        if (ch == '.') {
                            sb.append(ch);
                            this.next();
                            state = 5;
                        } else {
                            state = 6;
                        }
                        break;
                    case 4:
                        if (ch >= '0' && ch <= '9') {
                            sb.append(ch);
                            this.next();
                            // state not change
                        } else {
                            state = 3;
                        }
                        break;
                    case 5:
                        if (ch >= '0' && ch <= '9') {
                            sb.append(ch);
                            this.next();
                            // state not change
                        } else {
                            state = 6;
                        }
                        break;
                    case 6:
                        if (ch == 'e' || ch == 'E') {
                            sb.append(ch);
                            this.next();
                            state = 7;
                        } else {
                            state = 10;
                        }
                        break;
                    case 7:
                        if (ch == '+' || ch == '-') {
                            sb.append(ch);
                            this.next();
                        }
                        state = 8;
                        break;
                    case 8:
                        if (ch >= '0' && ch <= '9') {
                            sb.append(ch);
                            this.next();
                            state = 9;
                        } else {
                            throw new AppJsonException("JsonLexer error! Expect a number but read char:" + ch + ",index:" + this.index());
                        }
                        break;
                    case 9:
                        if (ch >= '0' && ch <= '9') {
                            sb.append(ch);
                            this.next();
                            // state not change
                        } else {
                            state = 10;
                        }
                        break;
                    case 10:
                        return sb.toString();
                    default:
                        throw new AppJsonException("JsonLexer error! The char:" + ch + ",index:" + this.index());
                }
            }
        } catch (IOException e) {
            try {
                close();
            } catch (IOException ignore) {
            }
        }
        return null;
    }
}
